package com.upb.webserver.contract;

import cn.hutool.core.collection.CollectionUtil;
import com.upb.webserver.application.ApplicationManager;
import com.upb.webserver.chain.ChainManager;
import com.upb.webserver.chain.ChainService;
import com.upb.webserver.common.enums.ChainTypeEnum;
import com.upb.webserver.common.enums.PlatformEnum;
import com.upb.webserver.common.exception.WebServerException;
import com.upb.webserver.common.pojo.base.BasePageResponse;
import com.upb.webserver.common.pojo.base.BaseResponse;
import com.upb.webserver.common.pojo.contract.ReqSendTransVO;
import com.upb.webserver.common.pojo.contract.RspContractVO;
import com.upb.webserver.common.pojo.rest.fisco.chainmgr.req.ReqFiscoSendTransDto;
import com.upb.webserver.common.returncode.ConstantCode;
import com.upb.webserver.common.tools.JsonUtils;
import com.upb.webserver.company.CompanyDeveloperService;
import com.upb.webserver.dao.entity.TbApplication;
import com.upb.webserver.dao.mapper.TbApplicationMapper;
import com.upb.webserver.dao.mapper.TbChainMapper;
import com.upb.webserver.dao.mapper.TbContractMapper;
import com.upb.webserver.fisco.contract.FiscoContractService;
import com.upb.webserver.rest.fisco.FiscoChainMgrRestService;
import com.upb.webserver.user.UserManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.collections4.map.HashedMap;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

@Slf4j
@Service
public class ContractService {
    @Autowired
    private ContractManager contractManager;
    @Autowired
    private ApplicationManager applicationManager;
    @Autowired
    private FiscoChainMgrRestService fiscoChainMgrRestService;
    @Autowired
    private TbContractMapper contractMapper;
    @Autowired
    private UserManager userManager;
    @Autowired
    private TbChainMapper chainMapper;
    @Autowired
    private CompanyDeveloperService companyDeveloperService;
    @Autowired
    private ChainService chainService;
    @Autowired
    private ChainManager chainManager;
    @Autowired
    private FiscoContractService fiscoContractService;


    /**
     * delete by contractId.
     *
     * @param contractId
     * @return
     */
    public BaseResponse deleteContract(int applicationId, int contractId) {
        log.info("start exec method [deleteContract]. applicationId:{} contractId:{}", applicationId, contractId);
        TbApplication application = applicationManager.verifyApplicationId(applicationId);

        BaseResponse response = null;
        if (ChainTypeEnum.FISCO.getId() == application.getChainType()) {
            response = fiscoChainMgrRestService.deleteContract(application.getChainId(), application.getId(), contractId);
        }

        log.info("restResponse:{}", JsonUtils.objToString(response));
        if (response == null || !response.isSuccess()) {
            return response;

        }

        contractMapper.deleteByPrimaryKey(contractId);
        log.info("success exec method [deleteContract]. contractId:{}", contractId);
        return BaseResponse.initSuccess();
    }


    /**
     * send transaction.
     */
    public BaseResponse sendTransaction(ReqSendTransVO param) {
        log.info("start exec method [sendTransaction]. param:{}", JsonUtils.objToString(param));
        TbApplication tbApplication = applicationManager.verifyApplicationId(param.getApplicationId());

        //send transaction
        BaseResponse restResponse = null;
        if (ChainTypeEnum.FISCO.getId() == tbApplication.getChainType()) {
            ReqFiscoSendTransDto restParam = new ReqFiscoSendTransDto();
            BeanUtils.copyProperties(param, restParam);
//            restParam.setContractId(contract.getRemoteId());
//            restParam.setSignUserId(FiscoChainMgrRestService.getUserIdToSign(String.valueOf(contract.getApplicationId()), tbUser.getUuid()));
            restResponse = fiscoChainMgrRestService.sendTransaction(restParam);
        } else {
            log.warn("not support chainType:{}", tbApplication.getChainType());
            throw new WebServerException(ConstantCode.CHAIN_TYPE_NOT_SUPPORT);
        }

        log.info("restResponse:{}", JsonUtils.objToString(restResponse));
        return restResponse;
    }


    /**
     * @param platform
     * @param currentAccountId
     * @param pageNumber
     * @param pageSize
     * @param chainId
     * @param chainType
     * @param applicationId
     * @return
     */
    public BasePageResponse queryByPage(Byte platform, int currentAccountId, int pageNumber, int pageSize, Integer chainId, Byte contractStatus, Byte chainType, Integer applicationId, Boolean containDetailFields) {
        log.info("start exec method [queryByPage].platform:{} currentAccountId:{} pageNumber:{} pageSize:{} chainId:{} contractStatus:{} chainType:{} applicationId:{} containDetailFields:{}", platform, containDetailFields, pageNumber, pageSize, chainId, contractStatus, chainType, applicationId, containDetailFields);

        Map<String, Object> restParam = new HashedMap<>();
        if (PlatformEnum.DEVELOPER.getId() == platform) {
            restParam = initExampleForDeveloper(currentAccountId, pageNumber, pageSize, chainId, contractStatus, chainType, applicationId);
        } else {
            TbApplication applicationById = applicationManager.verifyApplicationId(applicationId);
            restParam.put("chainIds", Arrays.asList(chainId));
            restParam.put("appIds", Arrays.asList(applicationById.getFinalName()));
        }

        if (Objects.isNull(restParam)) {
            log.info("finish exec method[queryByPage] ,initExample result is null");
            return BasePageResponse.initialize();
        }

        restParam.put("pageNumber", pageNumber <= 0 ? 1 : pageNumber);
        restParam.put("pageSize", pageSize <= 0 ? 20 : pageSize);
        restParam.put("contractStatus", contractStatus);
        restParam.put("containDetailFields", containDetailFields);

        BasePageResponse basePageResponse = new BasePageResponse(ConstantCode.SUCCESS);
        List<RspContractVO> respList = new ArrayList<>();
        List<RspContractVO> fiscoList = null;

        if (chainType == 0 || chainType == -1) {
            try {
                fiscoList = fiscoContractService.listContractByParam(restParam);
            } catch (Exception ex) {
                log.info("failed exec method [queryByPage] for fisco");
            }
            if (fiscoList != null) {
                respList.addAll(fiscoList);
            }
        }
        basePageResponse.setTotalCount(respList.size());
        basePageResponse.setData(respList);

        log.debug("success exec method [queryByPage]. basePageResponse:{}", JsonUtils.objToString(basePageResponse));
        return basePageResponse;
    }

    @Autowired
    private TbApplicationMapper applicationMapper;



    /**
     * @param currentAccountId
     * @param pageNumber
     * @param pageSize
     * @param chainId
     * @param chainType
     * @param applicationId
     * @return
     */
    private Map<String, Object> initExampleForDeveloper(int currentAccountId, int pageNumber, int pageSize, Integer chainId, Byte contractStatus, Byte chainType, Integer applicationId) {
        log.info("start exec method [initExampleForDeveloper]. currentAccountId:{} pageNumber:{} pageSize:{} chainId:{} contractStatus:{} chainType:{} applicationId:{}", currentAccountId, pageNumber, pageSize, chainId, contractStatus, chainType, applicationId);
        int companyId = companyDeveloperService.selectCompanyIdByDeveloperId(currentAccountId);
        Set<String> applicationFinalNameSet = applicationManager.getFinalNameByCompanyId(companyId);
        Set<Integer> chainIdSet = chainManager.getChainIdSetByCompanyIdList(Arrays.asList(companyId));

        if (CollectionUtil.isEmpty(chainIdSet) && CollectionUtil.isEmpty(applicationFinalNameSet)) {
            log.info("not found application or chain by company:{}", companyId);
            return null;
        }

        //param:chainId
        if (Objects.nonNull(chainId)) {
            if (CollectionUtil.isNotEmpty(chainIdSet) && !chainIdSet.contains(chainId)) {
                log.warn("fail exec method [initExampleForDeveloper]. inputChainId:{} but support:{}", chainId, JsonUtils.objToString(chainIdSet));
                throw new WebServerException(ConstantCode.INVALID_CHAIN_ID);
            }
            chainIdSet = SetUtils.hashSet(chainId);
        }

        //param:applicationId
        if (Objects.nonNull(applicationId)) {
            TbApplication applicationById = applicationManager.verifyApplicationId(applicationId);
            if (CollectionUtil.isNotEmpty(applicationFinalNameSet) && !applicationFinalNameSet.contains(applicationById.getFinalName())) {
                log.warn("fail exec method [initExampleForDeveloper]. inputApplicationId:{} but support:{}", applicationId, JsonUtils.objToString(chainIdSet));
                throw new WebServerException(ConstantCode.APPLICATION_ID_INVALID);
            }
            applicationFinalNameSet = SetUtils.hashSet(applicationById.getFinalName());
        }

        //rest param
        Map<String, Object> restParam = new HashedMap<>();
        restParam.put("chainIds", chainIdSet);
        restParam.put("appIds", applicationFinalNameSet);


        log.info("success exec method[initExampleForDeveloper]. result:{}", JsonUtils.objToString(restParam));
        return restParam;
    }

}
